package de.g18.gruppe3.ldapinterface.service;

import java.util.Properties;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import de.g18.gruppe3.common.exception.WrongUsernameAndOrPasswordException;
import de.g18.gruppe3.common.model.AccountAdmin;

/**
 * Schnittstelle zum eigentlichen LDAP-Server, alle LDAP-Services bedienen diese Schnittstelle.
 * Bevor mit dem Context gearbeitet werden kann muss mit {@link #connect(AccountAdmin)} eine
 * Verbindung zum Server aufgebaut werden. Wird der Context nichtmehr benötigt sollte dieser über
 * {@link #close()} geschloßen werden.
 *
 * @author <a href="mailto:kevinhuber.kh@gmail.com">Kevin Huber</a>
 */
public class LDAPContext {

    /** Name des LDAP Servers */
    public static final String SERVER_NAME = "S215-03";

    /** Port des LDAP Servers */
    public static final int SERVER_PORT = 389;

	/** Name der Domain */
    public static final String DOMAIN_NAME = "Dom215-03.local";

	/** Wurzel der AD-Strucktur */
    public static final String DOMAIN_ROOT = "ou=Schüler,dc=Dom215-03,dc=local";

    /** Singelton-Instanz */
    private static LDAPContext instance;

    /** Die <code>DirContext</code> Verbindung zu LDAP */
    private DirContext context;


    private LDAPContext() {
    }

    /**
     * Versucht eine Verbinung zum LDAP herzustellen
     *
     * @param aAccountAdmin Account Daten mit denen sich am LDAP eingeloggt werden soll
     *
     * @throws WrongUsernameAndOrPasswordException Falls die Verbindung wegen
     *                                             Fehlerhafter Account-Daten incht aufgebaut werden konnte
     * @throws NamingException
     */
    public void connect(AccountAdmin aAccountAdmin) throws NamingException, WrongUsernameAndOrPasswordException {
        if (aAccountAdmin == null || aAccountAdmin.getLogin() == null
            || aAccountAdmin.getLogin().isEmpty() || aAccountAdmin.getPasswort() == null
            || aAccountAdmin.getPasswort().isEmpty()) {
            throw new WrongUsernameAndOrPasswordException();
        }
        String providerURL = "ldap://" + SERVER_NAME + ":" + SERVER_PORT;

        Properties props = new Properties();
        props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        props.put(Context.PROVIDER_URL, providerURL);

        if (aAccountAdmin.getLogin() != null && !aAccountAdmin.getLogin().isEmpty()) {
            props.put(Context.SECURITY_AUTHENTICATION, "simple");
            props.put(Context.SECURITY_PRINCIPAL, aAccountAdmin.getLogin());
            props.put(Context.SECURITY_CREDENTIALS, aAccountAdmin.getPasswort() == null ? "" : aAccountAdmin.getPasswort());
        }
        context = new InitialDirContext(props);
    }

    public static LDAPContext getInstance() {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
    }

    private static synchronized LDAPContext createInstance() {
        return new LDAPContext();
    }

    /**
     * @see DirContext#createSubcontext(String, Attributes)
     */
    public DirContext createSubcontext(String aName, Attributes aAttributes) throws NamingException {
        checkContext();
    	return context.createSubcontext(aName, aAttributes);
    }

    /**
     * @see DirContext#search(String, String, SearchControls)
     */
    public NamingEnumeration<SearchResult> search(String aName, String aFilter, SearchControls aControls) throws NamingException {
        checkContext();
        return context.search(aName, aFilter, aControls);
    }

    /**
     * @see DirContext#modifyAttributes(String, ModificationItem[])
     */
    public void modifyAttributes(String aName, ModificationItem[] aModifications) throws NamingException {
        checkContext();
        context.modifyAttributes(aName, aModifications);
    }

    /**
     * @see DirContext#destroySubcontext(String)
     */
    public void destroySubcontext(String aName) throws NamingException {
        context.destroySubcontext(aName);
    }

    /**
     * @see DirContext#close()
     */
    public void close() throws NamingException {
        checkContext();
        context.close();
    }

    private void checkContext() {
        if (!isContextAvailable()) {
            throw new IllegalStateException("Context wurde nicht initialisiert!");
        }
    }

    /**
     * PrÃ¼ft ob ein DirContext besteht
     * @return True falls ein DirContext besteht
     */
    public boolean isContextAvailable() {
        return context != null;
    }
}
